package org.xx.armory.obs.controllers;

import com.obs.services.IObsClient;
import com.obs.services.model.ListObjectsRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.CacheControl;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;
import org.xx.armory.commons.ForLogging;
import org.xx.armory.obs.FileRef;
import org.xx.armory.obs.ObsAuthorization;
import org.xx.armory.obs.ObsDeniedException;

import java.io.IOException;
import java.util.concurrent.TimeUnit;

import static java.util.stream.Collectors.toList;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.xx.armory.commons.ResponseUtils.encodeRFC2231;
import static org.xx.armory.spring5.mvc.ModelUtils.buildJsonView;
import static org.xx.armory.spring5.mvc.ModelUtils.paramNotBlank;
import static org.xx.armory.spring5.mvc.UploadFileUtils.makeObjectKey;
import static org.xx.armory.spring5.mvc.UploadFileUtils.simpleFileName;

/**
 * hw obs Controller。
 */
@RequestMapping("/huawei-obs")
public class HuaweiObsController {
    public static final String ACTION_DOWNLOAD = "download";

    public static final String ACTION_GET = "get";

    @ForLogging
    private final Logger logger = LoggerFactory.getLogger(HuaweiObsController.class);

    @Value("${armory.hw-obs.bucket}")
    private String obsBucket;

    @Autowired
    private IObsClient obsClient;

    @Autowired(required = false)
    private ObsAuthorization obsAuthorization;

    @PostMapping("/{root:[a-z0-9-]+}/{obj-id:\\d+}/{obj-type:\\d+}")
    public ModelAndView upload(
            @PathVariable("root") String root,
            @PathVariable("obj-id") long objId,
            @PathVariable("obj-type") int objType,
            @RequestParam(value = "alt-file-name",
                          required = false) String altFileName,
            @RequestParam("file") MultipartFile file,
            Authentication token
    )
            throws Exception {
        // noinspection ConstantConditions
        final var oFileName = paramNotBlank(file.getOriginalFilename(), "file.name").strip();
        final var fileName = isBlank(altFileName) ? simpleFileName(oFileName) : altFileName.strip();

        if (this.obsAuthorization != null && !this.obsAuthorization.allowUpload(root, objId, objType, fileName, file.getSize(), token)) {
            throw new ObsDeniedException();
        }

        final var pr = this.obsClient.putObject(this.obsBucket, makeObjectKey(root, objId, objType, fileName), file.getInputStream());

        return buildJsonView(new FileRef(fileName, pr.getObjectKey(), file.getSize(), pr.getEtag()));
    }

    @GetMapping("/{root:[a-z0-9-]+}/{obj-id:\\d+}/{obj-type:\\d+}")
    public ModelAndView list(
            @PathVariable("root") String root,
            @PathVariable("obj-id") long objId,
            @PathVariable("obj-type") int objType,
            Authentication token
    ) {
        if (this.obsAuthorization != null && !this.obsAuthorization.allowList(root, objId, objType, token)) {
            throw new ObsDeniedException();
        }

        final var request = new ListObjectsRequest(this.obsBucket, makeObjectKey(root, objId, objType, ""), null, null, 0);
        return buildJsonView(
                this.obsClient.listObjects(request)
                              .getObjects()
                              .stream()
                              .map(obsObj -> {
                                       final var meta = obsObj.getMetadata();
                                       return new FileRef(
                                               simpleFileName(obsObj.getObjectKey()),
                                               obsObj.getObjectKey(),
                                               meta.getContentLength(),
                                               meta.getEtag()
                                       );
                                   }
                              )
                              .collect(toList())
        );
    }

    @DeleteMapping("/{root:[a-z0-9-]+}/{obj-id:\\d+}/{obj-type:\\d+}/{file-name}")
    public ModelAndView delete(
            @PathVariable("root") String root,
            @PathVariable("obj-id") long objId,
            @PathVariable("obj-type") int objType,
            @PathVariable("file-name") String fileName,
            Authentication token
    ) {
        if (this.obsAuthorization != null && !this.obsAuthorization.allowDelete(root, objId, objType, fileName, token)) {
            throw new ObsDeniedException();
        }

        final var dr = this.obsClient.deleteObject(this.obsBucket, makeObjectKey(root, objId, objType, fileName));
        return buildJsonView(
                new FileRef(
                        simpleFileName(dr.getObjectKey()),
                        dr.getObjectKey())
        );
    }

    @GetMapping("/{root:[a-z0-9-]+}/{obj-id:\\d+}/{obj-type:\\d+}/{file-name}")
    public ResponseEntity<InputStreamResource> download(
            @PathVariable("root") String root,
            @PathVariable("obj-id") long objId,
            @PathVariable("obj-type") int objType,
            @PathVariable("file-name") String fileName,
            @RequestParam(value = "action",
                          defaultValue = ACTION_GET) String action,
            @RequestHeader(value = "If-None-Match",
                           required = false) String ifNoneMatch,
            Authentication token
    )
            throws IOException {
        if (this.obsAuthorization != null && !this.obsAuthorization.allowDownload(root, objId, objType, fileName, token)) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
        }

        // 判断obs对象是否存在
        final var existsObsObject = this.obsClient.doesObjectExist(this.obsBucket, makeObjectKey(root, objId, objType, fileName));
        if (!existsObsObject) {
            return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
        }

        // 获取obs对象
        final var obsObject = this.obsClient.getObject(this.obsBucket, makeObjectKey(root, objId, objType, fileName));
        if (ACTION_GET.equals(action) && obsObject.getMetadata().getEtag().equals(ifNoneMatch)) {
            return ResponseEntity.status(HttpStatus.NOT_MODIFIED).build();
        }

        final var contentType = obsObject.getMetadata().getContentType();

        var builder = ResponseEntity.ok()
                                    .contentLength(obsObject.getMetadata().getContentLength());

        if (contentType.startsWith("text/") && !contentType.contains(";")) {
            builder = builder.contentType(MediaType.valueOf(contentType + ";charset=UTF8"));
        } else {
            builder = builder.contentType(MediaType.valueOf(contentType));
        }
        if (ACTION_GET.equals(action)) {
            builder = builder
                    .cacheControl(CacheControl.maxAge(60 * 24, TimeUnit.MINUTES))
                    .eTag(obsObject.getMetadata().getEtag())
                    .varyBy("If-None-Match");
        } else if (ACTION_DOWNLOAD.equals(action)) {
            builder = builder.header("Content-Disposition", "attachment;filename*=" + encodeRFC2231(simpleFileName(obsObject.getObjectKey())));
        }

        return builder.body(new InputStreamResource(obsObject.getObjectContent()));
    }
}
